package com.acme.bankapp.domain;

import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;

import com.acme.bankapp.domain.Bank;
import com.acme.bankapp.domain.Bank.ClientRegistrationListener;
import com.acme.bankapp.domain.Client;
import com.acme.bankapp.domain.accounting.Account;
import com.acme.bankapp.domain.accounting.CheckingAccount;
import com.acme.bankapp.domain.accounting.NotEnoughFundsException;
import com.acme.bankapp.domain.accounting.SavingAccount;
import com.acme.bankapp.services.DataWorker;

public class BankService {

	private DataWorker dataWorker;

	public BankService() {
		createBank(new LinkedList<Bank.ClientRegistrationListener>());
	}

	/**
	 * Controlled bank
	 */
	private Bank bank;

	public Bank getBank() {
		return bank;
	}

	/**
	 * Sets DataWorker for BankService. BankService cab backup bank to
	 * DataWorker every time smth changed
	 * 
	 * @param pathname
	 *            file path for DataFile
	 */
	public void setDataWorker(DataWorker worker) {
		this.dataWorker = worker;
	}

	private void addAccount(Client client, Account account)
			throws AccountExistsException {
		if (!client.addAccount(account)) {
			throw new AccountExistsException(account.toString(),
					client.toString());
		}
		saveBank();
	}

	/**
	 * Add new saving account to client
	 * 
	 * @param client
	 * @throws AccountExistsException
	 */
	public SavingAccount addSavingAccount(Client client) throws AccountExistsException {
		SavingAccount savingAccount = new SavingAccount(++bank.lasAccountId);
		addAccount(client, savingAccount);
		return savingAccount;
	}

	/**
	 * Add checking account to client with minOverdraft
	 * 
	 * @param client
	 * @param minOverdraft
	 * @throws AccountExistsException
	 */
	public CheckingAccount addCheckingAccount(Client client, double minOverdraft)
			throws AccountExistsException {
		CheckingAccount checkingAccount = new CheckingAccount(
				++bank.lasAccountId, minOverdraft);
		addAccount(client, checkingAccount);
		return checkingAccount;
	}

	/**
	 * Add checking account to client with defaultOverdraft
	 * 
	 * @param client
	 * @throws AccountExistsException
	 */
	public CheckingAccount addCheckingAccount(Client client) throws AccountExistsException {
		CheckingAccount checkingAccount = new CheckingAccount(
				++bank.lasAccountId);
		addAccount(client, checkingAccount);
		return checkingAccount;
	}

	/**
	 * Deposit to account
	 * 
	 * @param account
	 *            Account
	 * @param x
	 *            Value to deposit
	 */
	public void deposit(Account account, double x) {
		account.deposit(x);

		saveBank();
	}

	/**
	 * Withdraw from account
	 * 
	 * @param account
	 *            Account
	 * @param x
	 *            Value to withdraw
	 * @throws LowBalanceException
	 *             Error withdrawing if X is greater than available amount to
	 *             withdraw
	 */
	public void withdraw(Account account, double x)
			throws NotEnoughFundsException {
		account.withdraw(x);

		saveBank();
	}

	/**
	 * new bank
	 * 
	 * @return clean bank
	 */
	public void createBank(Collection<ClientRegistrationListener> listeners) {
		Bank bank = new Bank(listeners, new HashSet<Client>());
		this.bank = bank;
	}

	/**
	 * Add client registration listener to bank
	 * 
	 * @param listener
	 */
	public void addClientRegistrationListener(
			ClientRegistrationListener listener) {
		this.bank.addClientRegistrationListener(listener);
	}

	/**
	 * Save serialized bank to file
	 * 
	 * @param bank
	 *            Bank object
	 * @param filename
	 *            filename
	 */
	public void saveBank() {
		if (dataWorker == null)
			return;
		try {
			dataWorker.save(bank);
		} catch (IOException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * Restore bank from serialized object
	 * 
	 * @return Bank
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public void loadBank() throws IOException {
		if (dataWorker == null)
			return;
		try {
			Collection<ClientRegistrationListener> listeners = bank
					.getClientRegistrationListeners();
			this.bank = (Bank) dataWorker.restore();
			this.bank.setClientRegistrationListeners(listeners);
		} catch (ClassNotFoundException e) {
			System.out.println("Bad bank file");
		}
	}

	/**
	 * Fill banks clients from external DataWorker file
	 * 
	 * @param feedWorker
	 *            external DataWorker file, which incapsulate path to file with
	 *            feed info
	 */
	public void fillClients(DataWorker feedWorker) {

		String feedString = feedWorker.loadFeed();

		try {
			for (String clientString : feedString.split("\n")) {
				String[] clientInfo = clientString.split(":", 3);
				String name = clientInfo[0];
				Gender gender = (clientInfo[1].equals("m")) ? Gender.MALE
						: Gender.FEMALE;
				Client client = addClient(name, gender);
				for (String accountString : clientInfo[2].split(";")) {
					String[] accountInfo = accountString.split("/");
					Account account = null;
					if ("s".equals(accountInfo[0])) {
						account = new SavingAccount(
								Long.parseLong(accountInfo[1]),
								Double.parseDouble(accountInfo[2]));
					} else if ("c".equals(accountInfo[0])) {
						account = new CheckingAccount(
								Long.parseLong(accountInfo[1]),
								Double.parseDouble(accountInfo[2]),
								Double.parseDouble(accountInfo[3]));
					}
					if (account != null)
						addAccount(client, account);
				}
				this.bank.addClient(client);
			}
		} catch (ClientExistsException e) {
			System.err.println("Error filling clients!");
		} catch (AccountExistsException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Creates client with hashset of accounts to provide faster access to client's
	 * account
	 * 
	 * @param name
	 *            Client name
	 * @param gender
	 *            Client gender
	 * @return Client
	 * @throws ClientExistsException 
	 */
	public Client addClient(String name, Gender gender) throws ClientExistsException {
		Client client = new Client(name, gender);
		client.setAccounts(new HashSet<Account>());
		bank.addClient(client);

		saveBank();
		
		return client;
	}

}
